from collections import OrderedDict
from backtrader.stores.binancestore import CCXTStore
import backtrader as bt
import click
from backtrader.utils import logger, datehelper
from backtrader.utils.cmd import cli
from datetime import datetime, timedelta
import argparse
import json
import os


class BinanceRun:
    def __init__(self, **kwargs):
        self.logger = logger.getLogger(__name__)
        self.account = bt.AccountMgt().get_account(kwargs["account"])
        self.logger.info("%s Input Args %s", "*" * 15, "*" * 15)
        self.print_params(kwargs)

        token = kwargs["alert_token"]
        alert_enable = True if kwargs["alert_enable"].upper() == "TRUE" else False

        _debug = True if kwargs["debug"].upper() == "TRUE" else False

        self.debug = _debug

        self.alert = bt.utils.Alert(token, alert_enable)
        self.strat_code = kwargs["strat"]
        self.symbols = kwargs["symbols"]
        self.currency = list(map(lambda x: x.replace("/USDT", ""), self.symbols))
        self.asset_type = kwargs["type"]
        self.time_frame = kwargs["timeframe"]
        self.resample = kwargs["resample"]
        self.exchange = kwargs["exchange"]
        self.params = kwargs["params"]
        self.max_amount = kwargs["max_amount"]
        self.max_size = kwargs["max_size"]
        self.ordtype = kwargs["ordtype"]
        self.slippage = kwargs["slippage"]
        self.lookback = kwargs["lookback"]
        self.max_trade = kwargs["max_trade"]
        self.store = None
        self.init_exchange()

    def print_strategy_config(self):
        for k in self.params.keys():
            self.logger.info("Strategy: %s=%s", k, self.params[k])

    def init_exchange(self):
        # Create our store
        config = {
            "apiKey": self.account.apiKey,
            "secret": self.account.secret,
            "password": self.account.password,
            "options": {
                'recvWindow': 60000,
                "createMarketBuyOrderRequiresPrice": True,
                "warnOnFetchOpenOrdersWithoutSymbol": False
            },
            "enableRateLimit": True
        }

        self.store = CCXTStore(exchange=self.exchange, currency=self.currency, config=config, retries=5)


    def print_params(self, kwargs):
        self.logger.info(kwargs)

    def run(self):
        timeframe, compression, total_minutes = bt.utils.datehelper.parse_timeframe(self.time_frame)
        history_minutes = (self.lookback + 1) * total_minutes
        hist_start_date = datetime.utcnow() - timedelta(minutes=history_minutes)

        ohlcv_limit = self.lookback + 1
        self.logger.info("%s Program Args %s", "*" * 15, "*" * 15)
        self.logger.info("Start: exchange=%s", self.exchange)
        self.logger.info("Start: @@@@【account=%s】@@@@", self.account.name)
        self.logger.info("Start: @@@@【max amount=%s】@@@@", self.max_amount)
        self.logger.info("Start: @@@@【max size=%s】@@@@", self.max_size)
        self.logger.info("Start: @@@@【max ordtype=%s】@@@@", self.ordtype)
        self.logger.info("Start: @@@@【max slippage=%s】@@@@", self.slippage)
        self.logger.info("Start: symbol=%s", ",".join(self.symbols))
        self.logger.info("Start: timeframe=%s", self.time_frame)
        self.logger.info("Start: history_minutes=%s", history_minutes)
        self.logger.info("Start: hist_start_date=%s", hist_start_date)
        self.logger.info("Start: limit=%s", ohlcv_limit)
        self.logger.info("%s", "*" * 40)

        # self.logger.info("%s Account Args %s", "*" * 15, "*" * 15)
        # self.print_account_config()

        self.logger.info("%s Strategy Args %s", "*" * 15, "*" * 15)
        self.print_strategy_config()

        cerebro = bt.Cerebro(quicknotify=True)

        strategy_name = strategy_code = self.strat_code
        strat = __import__(strategy_code)

        strat_names = [e for e in strat.__dict__.keys() if e.endswith("Strategy")]
        if not strat_names:
            raise Exception(f"策略名称必须以Strategy结尾.")
        strat_clz = strat_names[0]

        init_params = self.params.copy()
        init_params["name"] = strategy_name
        init_params["max_amount"] = self.max_amount
        init_params["max_size"] = self.max_size
        init_params["ordtype"] = self.ordtype
        init_params["slippage"] = self.slippage
        init_params["token"] = self.alert.token
        init_params["max_trade"] = self.max_trade
        init_params["enable"] = self.alert.enable
        init_params["debug"] = self.debug

        self.logger.info("Load Strategy: class=%s", strat_clz)
        self.logger.info("Load Strategy: name=%s", strategy_name)
        self.logger.info("Load Strategy: param=%s", init_params)


        # # Add the strategy
        cerebro.addstrategy(strat.__dict__[strat_clz], **init_params)

        broker_mapping = {
            'order_types': {
                bt.Order.Market: 'market',
                bt.Order.Limit: 'limit',
                bt.Order.Stop: 'stop_loss',  # stop-loss for kraken, stop for bitmex
                bt.Order.StopLimit: 'stop_loss_limit'
            },
            'mappings': {
                'closed_order': {
                    'key': 'status',
                    'value': 'closed'
                },
                'canceled_order': {
                    'key': 'status',
                    'value': 'canceled'
                },
                "order_result": {
                    'id': 'id',
                    'symbol': 'symbol',
                    'exectype': 'type',
                    'side': "side",  #
                    'price': "price", # 委托价
                    'stop_price': "stopPrice", # 止损止盈触发价
                    'size': "amount",
                    'trade_dt': "timestamp",  # 成交时间
                    'trade_price': "average",  # 成交价
                },
            }
        }

        broker = self.store.getbroker(broker_mapping=broker_mapping)
        cerebro.setbroker(broker)

        for symbol in self.symbols:
            symbol_name = symbol.replace("/", "_")
            data = self.store.getdata(dataname=symbol, name=symbol_name,
                                      timeframe=timeframe, fromdate=hist_start_date,
                                      compression=compression, ohlcv_limit=ohlcv_limit, drop_newest=True)  # , historical=True)

            # Add the feed
            cerebro.adddata(data, name=symbol_name)

            if self.resample:
                # 这里必须设置 bar2edge=False, 否则会报错
                _resample, _compression, _ = bt.utils.datehelper.parse_timeframe(self.resample)
                cerebro.resampledata(dataname=data, name=symbol_name + f"_{self.resample}",
                                     timeframe=_resample, compression=_compression, bar2edge=False)


        try:
            # Run the strategy
            dtstr = datehelper.date2str(datetime.now())
            detail = "\naccount：%s\nmax_amount: %s\ntime_frame: %s\n" \
                     "max_size: %s\nordtype: %s\nslippage: %s\n策略参数: %s" % \
                     (self.account.name[0], self.max_amount, self.time_frame,
                      self.max_size, self.ordtype, self.slippage, self.params)
            self.alert.send(self.alert.format().format(name=strategy_name,
                                                       asset=",".join(self.symbols),
                                                       date=dtstr,
                                                       action="程序启动",
                                                       detail=detail))

            cerebro.run(runonece=False)
        except Exception as e:
            dtstr = datehelper.date2str(datetime.now())
            self.alert.send(self.alert.format().format(name=strategy_name,
                                                       asset=",".join(self.symbols),
                                                       date=dtstr,
                                                       action="程序崩溃",
                                                       detail="无"))
            raise e


# 必须指定默认值, 否则传进去的就是字符串
@cli.command()
@click.option("--cfg", default=None)
@click.option('--symbols', default="BTC/USDT,ETH/USDT")
def run(**kwargs):

    kwargs["symbols"] = kwargs["symbols"].split(",") if kwargs["symbols"] else None

    prom_dir = os.path.dirname(__file__)
    cfg_file = os.path.join(prom_dir, kwargs["cfg"])
    with open(cfg_file, "r") as f:
        args = json.load(f)
    for k, v in kwargs.items():
        if v or k not in args:
            args[k] = v
    BinanceRun(**args).run()

@click.group()
def binanceRun():
    pass

binanceRun.add_command(run)

if __name__ == "__main__":
    binanceRun()

